package com.liy.framework.common.orm.mybatis.interceptor;

import com.liy.framework.common.orm.dialect.Dialect;
import com.liy.framework.common.orm.dialect.DialectFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import com.liy.framework.common.vo.PageVO;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
public class PaginationStatementHandlerInterceptor implements Interceptor {

    private static final Logger logger;
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY;
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY;
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY;


    @Override
    public Object intercept(final Invocation invocation) throws Throwable {
        final StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        final ParameterHandler parameterHandler = statementHandler.getParameterHandler();
        final BoundSql boundSql = statementHandler.getBoundSql();
        final MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, PaginationStatementHandlerInterceptor.DEFAULT_OBJECT_FACTORY, PaginationStatementHandlerInterceptor.DEFAULT_OBJECT_WRAPPER_FACTORY, PaginationStatementHandlerInterceptor.DEFAULT_REFLECTOR_FACTORY);
        final RowBounds rowBounds = (RowBounds)metaStatementHandler.getValue("delegate.rowBounds");
        if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
            return invocation.proceed();
        }
        final Configuration configuration = (Configuration)metaStatementHandler.getValue("delegate.configuration");
        final Dialect dialect = DialectFactory.buildDialect(configuration);
        final String originalSql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
        final com.liy.framework.common.vo.PageVO<?> page = (PageVO<?>)rowBounds;
        final String countSql = dialect.getQueryCountString(originalSql);
        final Connection connection = (Connection)invocation.getArgs()[0];
        final int total = this.getTotal(parameterHandler, connection, countSql);
        page.setTotalCount(total);
        metaStatementHandler.setValue("delegate.boundSql.sql", dialect.getQueryDataString(originalSql, page.getOffset(), page.getLimit()));
        metaStatementHandler.setValue("delegate.rowBounds.offset", 0);
        metaStatementHandler.setValue("delegate.rowBounds.limit", Integer.MAX_VALUE);
        if (PaginationStatementHandlerInterceptor.logger.isDebugEnabled()) {
            PaginationStatementHandlerInterceptor.logger.debug(" SQL : " + boundSql.getSql());
        }
        return invocation.proceed();
    }


    @Override
    public Object plugin(final Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(final Properties properties) {}


    private int getTotal(final ParameterHandler parameterHandler, final Connection connection, final String countSql) throws Exception {

        PreparedStatement prepareStatement = null;

        ResultSet rs = null;

        int count = 0;

        try {
            prepareStatement = connection.prepareStatement(countSql);
            parameterHandler.setParameters(prepareStatement);
            rs = prepareStatement.executeQuery();
            if (rs.next()) {
                count = rs.getInt(1);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
            try {
                if (rs != null) {
                    rs.close();
                }
                if (prepareStatement != null) {
                    prepareStatement.close();
                }
            }
            catch (SQLException e2) {
                e2.printStackTrace();
            }
        }
        finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (prepareStatement != null) {
                    prepareStatement.close();
                }
            }
            catch (SQLException e3) {
                e3.printStackTrace();
            }
        }
        return count;
    }


    static {
        logger = LoggerFactory.getLogger(PaginationStatementHandlerInterceptor.class);
        DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
        DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
        DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    }
}
